import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { Common } from '../common/common';
import Redis from 'ioredis';
import { RedisService } from '@liaoliaots/nestjs-redis';
import { InjectRepository } from '@nestjs/typeorm';
import { Votes } from '../../module/Votes';
import { Repository } from 'typeorm';
import { Users } from '../../module/Users';
import { VoteInterface } from './interface/vote.interface';
import * as moment from 'moment';
import { RedisConfig } from '../../@nt/config';
import { CrontabService } from '../crontab/crontab.service';
import { ElectionSettings } from '../../module/ElectionSettings';

@Injectable()
export class VoteService extends Common implements OnModuleInit {
  private readonly logger = new Logger(VoteService.name);
  private readonly redis: Redis | null;

  constructor(
    @InjectRepository(Votes)
    private readonly voteRepository: Repository<Votes>,
    @InjectRepository(Users)
    private readonly userRepository: Repository<Users>,
    @InjectRepository(ElectionSettings)
    private readonly electionSettingRepository: Repository<ElectionSettings>,
    private readonly redisService: RedisService,
    private readonly crontabService: CrontabService,
  ) {
    super();
    this.redis = this.redisService.getOrThrow();
  }

  onModuleInit() {
    this.crontabService.defineJob(
      'election.end.job',
      this.expireSettingJob.bind(this),
    );
  }

  async expireSettingJob(job: any, done: any): Promise<void> {
    this.logger.log('在vote.service.ts中执行定时任务');
    const data: any = job;
    this.persistVotes(data.id);
    done();
  }

  /**
   * 投票，先放到redis，投票结束后再存入数据库
   * @param body
   */
  async submitVote(body: VoteInterface): Promise<any> {
    const lockKey = `${RedisConfig.voteLock}${body.userId}`;
    const where: any = {
      id: body.userId,
    };
    const userInfo = await this.userRepository.findOne({ where });
    if (!userInfo.isVerified) {
      await this.redis.del(lockKey);
      return this.returnJson(-1, null, '邮箱未验证');
    }

    const electionSettingId = await this.redis.get(RedisConfig.currentElection);
    if (!electionSettingId) {
      await this.redis.del(lockKey);
      return this.returnJson(-1, null, '当前无激活的选举场次');
    }

    const electionSettings: any = await this.redis.hgetall(
      `${RedisConfig.electionSetting}${electionSettingId}`,
    );
    const now = moment().unix();
    if (now < electionSettings.start_time || now > electionSettings.end_time) {
      await this.redis.del(lockKey);
      return this.returnJson(-1, null, '当前不在投票时间内');
    }

    const lock = await this.redis.set(lockKey, 'locked', 'EX', 5, 'NX'); // 5秒锁

    if (!lock) {
      return this.returnJson(-1, null, '操作过于频繁，请稍后重试');
    }
    const totalCandidates = await this.redis.scard(
      `${RedisConfig.electionTotal}${electionSettingId}`,
    );
    const maxVotes = Math.max(Math.floor(totalCandidates * 0.2), 2);

    const currentVotes = await this.redis.zcard(
      `${RedisConfig.electionUserVotePrefix}${userInfo.id}:${electionSettingId}${RedisConfig.electionUserVoteSuffix}`,
    );
    if (currentVotes >= maxVotes) {
      return this.returnJson(-1, null, '投票次数已达上限');
    }

    const existingScore = await this.redis.zscore(
      `${RedisConfig.electionUserVotePrefix}${userInfo.id}:${electionSettingId}${RedisConfig.electionUserVoteSuffix}`,
      `${body.candidateId}`,
    );
    if (existingScore !== null) {
      return this.returnJson(-1, null, '不可重复投票');
    }

    await this.redis.zadd(
      `${RedisConfig.electionUserVotePrefix}${userInfo.id}:${electionSettingId}${RedisConfig.electionUserVoteSuffix}`,
      now,
      `${body.candidateId}`,
    );

    await this.redis.hincrby(
      `${RedisConfig.electionCandidateVotes}${electionSettingId}`,
      `${body.candidateId}`,
      1,
    );

    await this.redis.del(lockKey);
    return this.returnJson(0, true, '投票成功');
  }

  /**
   * 选举场次结束，持久化到数据库
   * @param electionSettingId
   */
  async persistVotes(electionSettingId: number) {
    const userVoteKeys = [];
    let cursor = '0';
    do {
      const [newCursor, keys] = await this.redis.scan(
        cursor,
        'MATCH',
        `${RedisConfig.electionUserVotePrefix}*:${electionSettingId}${RedisConfig.electionUserVoteSuffix}`,
        'COUNT',
        1000,
      );
      userVoteKeys.push(...keys);
      cursor = newCursor;
    } while (cursor !== '0');

    const batchSize = 500;
    for (let i = 0; i < userVoteKeys.length; i += batchSize) {
      const batchKeys = userVoteKeys.slice(i, i + batchSize);
      const pipeline = this.redis.pipeline();

      batchKeys.forEach((key) => pipeline.zrange(key, 0, -1, 'WITHSCORES'));
      const results = await pipeline.exec();

      const voteEntities: Votes[] = [];
      for (const [keyIndex, key] of batchKeys.entries()) {
        const userId = key.split(':')[2]; // 从键中提取用户ID
        const candidateEntries = results[keyIndex][1] as string[];

        for (let j = 0; j < candidateEntries.length; j += 2) {
          const candidateId = candidateEntries[j];
          const votedAt = parseInt(candidateEntries[j + 1], 10);
          voteEntities.push(
            this.voteRepository.create({
              userId: parseInt(userId),
              candidateId: parseInt(candidateId),
              electionSettingId: electionSettingId,
              votedAt,
            }),
          );
        }
      }

      if (voteEntities.length > 0) {
        await this.voteRepository
          .createQueryBuilder()
          .insert()
          .into(Votes)
          .values(voteEntities)
          .orIgnore()
          .execute();
      }

      await this.redis.del(...batchKeys);

      const criteria: any = { id: electionSettingId, isActive: 1 };
      const partialEntity: any = { isActive: false };
      await this.electionSettingRepository.update(criteria, partialEntity);
    }

    this.logger.log(`选举场次 ${electionSettingId} 投票记录持久化完成`);
  }
}
